package util

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"
)

func GetConfigByEnv(config any) (err error) {

	var (
		_config = reflect.ValueOf(config).Elem()
	)

	for idx := 0; idx < _config.NumField(); idx++ {

		var (
			field = _config.Type().Field(idx)
			value = _config.Field(idx)

			env_tag   = field.Tag.Get("env")
			env_value = os.Getenv(env_tag)
		)

		if env_value == "" {
			err = fmt.Errorf("%s: not existed", env_tag)
			return
		}

		switch field.Type.Kind() {

		case reflect.String:

			value.SetString(env_value)

		case reflect.Int, reflect.Int64:

			var (
				number = int64(0)
			)

			if number, err = strconv.ParseInt(env_value, 10, 64); err != nil {
				err = fmt.Errorf("%s: %s", env_tag, err.Error())
				return
			}

			value.SetInt(number)

		default:

			err = fmt.Errorf("%s: only string|int|int64", env_tag)
			return
		}
	}

	return
}

func GetDeffient(old, new any, prefix string, diffrents map[string]any) {

	var (
		old_value = reflect.ValueOf(old).Elem()
		new_value = reflect.ValueOf(new).Elem()
	)

	if old_value.Type() != new_value.Type() {
		return
	}

	for idx := 0; idx < old_value.NumField(); idx++ {

		var (
			old_field = old_value.Field(idx)
			new_field = new_value.Field(idx)

			bson_tag string
			full_key string
		)

		if bson_tag = old_value.Type().Field(idx).Tag.Get("bson"); bson_tag == "" {
			continue
		}

		if prefix == "" {
			full_key = bson_tag
		} else {
			full_key = prefix + "." + bson_tag
		}

		switch old_field.Kind() {

		case reflect.Pointer, reflect.UnsafePointer:

			continue

		case reflect.Slice, reflect.Array:

			if old_field.Len() != new_field.Len() {

				diffrents[full_key] = new_field.Interface()

			} else {

				for i := 0; i < old_field.Len(); i++ {

					GetDeffient(old_field.Index(i).Addr().Interface(), new_field.Index(i).Addr().Interface(), full_key+"."+strconv.Itoa(i), diffrents)
				}
			}

		case reflect.Struct:

			GetDeffient(old_field.Addr().Interface(), new_field.Addr().Interface(), full_key, diffrents)

		default:

			if !reflect.DeepEqual(old_field.Interface(), new_field.Interface()) {
				diffrents[full_key] = new_field.Interface()
			}
		}
	}
}

func SetDifferent(old any, diffrents map[string]any) {

	old_value := reflect.ValueOf(old).Elem()

	for key, value := range diffrents {

		keys := strings.Split(key, ".")

		for idx, key_part := range keys {

			switch old_value.Kind() {

			case reflect.Slice, reflect.Array:

				if idx == len(keys)-1 {

					if i, err := strconv.Atoi(key_part); err == nil {

						if i < old_value.Len() {
							old_value.Index(i).Set(reflect.ValueOf(value))
						}
					}

				} else {

					if i, err := strconv.Atoi(key_part); err == nil && i < old_value.Len() {
						old_value = old_value.Index(i)
					} else {
						break
					}
				}

			default:

				field := old_value.FieldByNameFunc(func(name string) bool {
					return strings.ToLower(name) == key_part
				})

				if field.IsValid() {

					if idx == len(keys)-1 {
						field.Set(reflect.ValueOf(value))
					} else {
						old_value = field
					}
				}
			}
		}
	}
}

func ToJson(object any, space int) (result string) {

	var (
		data   []byte
		buffer bytes.Buffer

		err error
	)

	if data, err = json.Marshal(object); err != nil {
		return
	}

	if space == 0 {
		result = string(data)

	} else if err := json.Indent(&buffer, data, "", strings.Repeat(" ", space)); err != nil {
		result = string(data)

	} else {
		result = buffer.String()
	}

	return
}
